运算符优先级
非常重要,下面很多地方需要用到
[] == [] 为什么是 false?
数组是引用类型,是对地址的引用,虽然两个都为数组,但是在堆中的地址不同,所以比较结果为 false
为什么引用值要放在堆中,而原始值要放在栈中
记住一句话:能量是守衡的,无非是时间换空间,空间换时间的问题 堆比栈大,栈比堆的运算速度快,对象是一个复杂的结构,并且可以自由扩展,如:数组可以无限扩充,对象可以自由添加属性。将他们放在堆中是为了不影响栈的效率。而是通过引用的方式查找到堆中的实际对象再进行操作。相对于简单数据类型而言,简单数据类型就比较稳定,并且它只占据很小的内存。不将简单数据类型放在堆是因为通过引用到堆中查找实际对象是要花费时间的,而这个综合成本远大于直接从栈中取得实际值的成本。所以简单数据类型的值直接存放在栈中。
详情请戳 理解JS内存分配
为什么 [] == false 而 !![] == true ?
我们知道,非严格比较操作符 ==
是会做强制类型转换的,根据 ECMA 标准,规则如下:
- If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
- If Type(x) is Object and Type(y) is either String, Number, or Symbol, return the result of the comparison ToPrimitive(x) == y.
[] == false
所以 [] == false
的比较是对 x 执行 ToPrimitive, 然后和 ToNumber(false) 进行比较
1 | ToPrimitive(obj,preferredType) |
所以对于 [], 先调用 [].valueOf()
返回的结果是 [],不是原始值,继续执行[].toString()
返回 ""
所以最终比较的是 "" == 0
结果为 true
代码实现 toPrimitive 方法
1 | const toPrimitive = (obj, preferredType='Number') => { |
!![] == true
按照优先级, 会先执行 !
按照上图规范,实际上执行 !!ToBoolean([])
而 ToBoolean
的规则为
所以 ToBoolean([])
为 true, !![]
自然就为 true 了
这也是为什么我们不能用 if(!array)
来判断空数组而要用if(array.length === 0)
来判断空数组的原因。
valueOf() && toString()
上面提到了 valueOf()
和 toString()
两个方法,但具体为什么是那样的结果呢,鉴于待会最后一个案例还要用到,此时我们先来熟悉一下这两个方法。
toString()
可以看做是把一个数据转换成了相应字符串的形式,按下图规则转换
当valueOf
方法被调用时,会调用内置的ToObject
,并将this
作为参数传进去。ToObject
检测会根据参数类型进行数值的转换:
1 | Undefined - 抛出TypeError异常 |
加法中的隐式类型转换
当计算表达式 value1 + value2
的时候,会按照如下规则:
将两个操作数转换为原始值 (下面是数学表示法,不是JavaScript代码):
1
2prim1 := ToPrimitive(value1)
prim2 := ToPrimitive(value2)PreferredType 被省略,因此Date类型的值采用String,其他类型的值采用Number.
如果
prim1
或者prim2
中的任意一个为字符串,则将另外一个也转换成字符串,然后返回两个字符串连接操作后的结果.否则,将
prim1
和prim2
都转换为数字类型,返回他们的和.
练习时间
为了方便表达,x 表示左边的值, y 表示右边的值
[] == ![]
根据运算优先级, y 最终会变为 Boolean, 由前文图中可知,最后会变为toPrimitive(x) == ToNumber(!ToBoolean(y))
可知表达式为 true[] + []
调用toPrimitive
,结果为""
1
2
3
4
5
6
7
8
9> [] + {}
'[object Object]'
> 5 + new Number(7)
12
> 6 + { valueOf: function () { return 2 } }
8
> "abc" + { toString: function () { return "def" } }
'abcdef'
此上都是对于加号运算的规则分析得出的结果{} + {}
结果为 NaN, 这是为什么呢?浏览器在解析时,会把第一个{}
解释为一个空代码块,并忽略他,实为执行+{}
,这里的 + 是一元操作符,作用是将它后面的操作数转为数字,过程如下1
2
3
4
5+{}
Number({})
Number({}.toString()) // 因为{}.valueOf()不是原始值
Number("[object Object]")
NaN // 结果为 NaNtrue + 1
,这个要着重记录一下,因为看到的时候是百思不得姐,为什么是 2 呢,后来仔细去看toPrimitive
因为 true 是原始值,所以直接返回 true,所以 左右有一为 Number,执行 ToNumber(true)
得出结果为2
总结
1 | 我们总结一下==运算的规则: |